#include<formic/input/base.h>
#include<formic/mpi/interface.h>
#include<formic/command/command.h>
#include<formic/all_data.h>
#include<formic/archive.h>

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief   Function called for the "Option" command, which sets a user input option
///
/// \param[in]     arch         archive to read command arguments from
/// \param[in]     data         data on which to execute the command
///
///////////////////////////////////////////////////////////////////////////////////////////////////

static void formic_set_input_option(formic::Archive & arch, formic::AllData & data) {

  // read in the name of the input variable
  std::string name;
  formic::mpi::read_and_bcast(arch, name, "failed to read input option name from the supplied archive");
  //if ( !(arch >> name) )
  //  throw formic::Exception("failed to read input option name from the supplied archive");

  // read in and set the option value
  data.userinp.set_option(name, arch);

}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief   Add input related commands to the formic command system
///
///////////////////////////////////////////////////////////////////////////////////////////////////
void formic::init_input_commands() {

  formic::add_command("Option", &formic_set_input_option);

}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief   Chooses which option will be returned by the next type conversion.
///
///          Example of intended usage (InputDerived represents a child of InputBase):
///          
///            formic::InputDerived userinp;
///            const int n = userinp("nsites");
///            std::vector<double> v = userinp("site_potentials");
///
/// \param[in]     name         name of the option to be returned by the next conversion
///
/// \return  a reference to the object
///
///////////////////////////////////////////////////////////////////////////////////////////////////
formic::InputBase & formic::InputBase::operator()(const std::string & name) {

  if ( _bool_map.count(name) > 0 ) {
    _type_of_next = "bool";
    _next_bool = _bool_map[name];
  } else if ( _int_map.count(name) > 0 ) {
    _type_of_next = "int";
    _next_int = _int_map[name];
  } else if ( _long_int_map.count(name) > 0 ) {
    _type_of_next = "long int";
    _next_long_int = _long_int_map[name];
  } else if ( _double_map.count(name) > 0 ) {
    _type_of_next = "double";
    _next_double = _double_map[name];
  } else if ( _complex_map.count(name) > 0 ) {
    _type_of_next = "std::complex<double>";
    _next_complex = _complex_map[name];
  } else if ( _string_map.count(name) > 0 ) {
    _type_of_next = "std::string";
    _next_string = _string_map[name];
  } else if ( _vec_int_map.count(name) > 0 ) {
    _type_of_next = "std::vector<int>";
    _next_vec_int = _vec_int_map[name];
  } else if ( _vec_long_int_map.count(name) > 0 ) {
    _type_of_next = "std::vector<long int>";
    _next_vec_long_int = _vec_long_int_map[name];
  } else if ( _vec_double_map.count(name) > 0 ) {
    _type_of_next = "std::vector<double>";
    _next_vec_double = _vec_double_map[name];
  } else if ( _vec_complex_map.count(name) > 0 ) {
    _type_of_next = "std::vector<std::complex<double> >";
    _next_vec_complex = _vec_complex_map[name];
  } else if ( _vec_string_map.count(name) > 0 ) {
    _type_of_next = "std::vector<std::string>";
    _next_vec_string = _vec_string_map[name];
  } else {
    throw formic::Exception("formic::InputBase has no option named \"%s\"") % name;
  }

  return *this;

}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief   Sets the option of the given name by reading it from the provided archive
///
/// \param[in]      name     name of the option to set
/// \param[in,out]  arch     archive from which to read the option's value
///
///////////////////////////////////////////////////////////////////////////////////////////////////
void formic::InputBase::set_option(const std::string & name, formic::Archive & arch) {

  const std::string error_str = (boost::format("failed to read Input option \"%s\"") % name).str();

  if ( _bool_map.count(name) > 0 )
    formic::mpi::read_and_bcast(arch, _bool_map[name], error_str);
  else if ( _int_map.count(name) > 0 )
    formic::mpi::read_and_bcast(arch, _int_map[name], error_str);
  else if ( _long_int_map.count(name) > 0 )
    formic::mpi::read_and_bcast(arch, _long_int_map[name], error_str);
  else if ( _double_map.count(name) > 0 )
    formic::mpi::read_and_bcast(arch, _double_map[name], error_str);
  else if ( _complex_map.count(name) > 0 )
    formic::mpi::read_and_bcast(arch, _complex_map[name], error_str);
  else if ( _string_map.count(name) > 0 )
    formic::mpi::read_and_bcast(arch, _string_map[name], error_str);
  else if ( _vec_int_map.count(name) > 0 )
    formic::mpi::read_and_bcast(arch, _vec_int_map[name], error_str);
  else if ( _vec_long_int_map.count(name) > 0 )
    formic::mpi::read_and_bcast(arch, _vec_long_int_map[name], error_str);
  else if ( _vec_double_map.count(name) > 0 )
    formic::mpi::read_and_bcast(arch, _vec_double_map[name], error_str);
  else if ( _vec_complex_map.count(name) > 0 )
    formic::mpi::read_and_bcast(arch, _vec_complex_map[name], error_str);
  else if ( _vec_string_map.count(name) > 0 )
    formic::mpi::read_and_bcast(arch, _vec_string_map[name], error_str);
  else
    throw formic::Exception("formic::InputBase has no option named \"%s\"") % name;

  this->initialize_derived();

  this->verify();

}

// ///////////////////////////////////////////////////////////////////////////////////////////////////
// /// \brief   Sets a member's value using the remainder of the input command after the member name
// ///          has been read.
// ///
// ///          This is the default template behavior.
// ///          There are more specialized versions of this function for bools, vectors, and sets.
// ///
// /// \param[in]     member_name  name of the member being set
// /// \param[in,out] arch         an archive holding the member's value
// /// \param[out]    member       the member whose value will be set
// ///
// ///////////////////////////////////////////////////////////////////////////////////////////////////
// template<class T> void formic::InputBase::set_member(const std::string & member_name,
//                                                      formic::Archive & arch,
//                                                      T & member) {
// 
//   // read and broadcast the member's value
//   formic::mpi::read_and_bcast(arch, member, (boost::format("failed to read Input member \"%s\"") % member_name).str());
// 
// }
// 
// template void formic::InputBase::set_member(const std::string &, formic::Archive &, bool &);
// template void formic::InputBase::set_member(const std::string &, formic::Archive &, int &);
// template void formic::InputBase::set_member(const std::string &, formic::Archive &, long int &);
// template void formic::InputBase::set_member(const std::string &, formic::Archive &, double &);
// template void formic::InputBase::set_member(const std::string &, formic::Archive &, std::complex<double> &);
// template void formic::InputBase::set_member(const std::string &, formic::Archive &, std::string &);
// template void formic::InputBase::set_member(const std::string &, formic::Archive &, std::vector<int> &);
// template void formic::InputBase::set_member(const std::string &, formic::Archive &, std::vector<long int> &);
// template void formic::InputBase::set_member(const std::string &, formic::Archive &, std::vector<double> &);
// template void formic::InputBase::set_member(const std::string &, formic::Archive &, std::vector<std::complex<double> > &);
// template void formic::InputBase::set_member(const std::string &, formic::Archive &, std::vector<std::string> &);

namespace formic {

  ///  \brief  function to get bool  options by name
  template <> const bool  & InputBase::get<bool >(const std::string & name) const {
    if ( _bool_map.count(name) > 0 )
      return _bool_map.find(name)->second;
    throw formic::Exception("no bool matching name \"%s\" in InputBase::get") % name;
  }

  ///  \brief  function to get int  options by name
  template <> const int  & InputBase::get<int >(const std::string & name) const {
    if ( _int_map.count(name) > 0 )
      return _int_map.find(name)->second;
    throw formic::Exception("no int matching name \"%s\" in InputBase::get") % name;
  }

  ///  \brief  function to get long int  options by name
  template <> const long int  & InputBase::get<long int >(const std::string & name) const {
    if ( _long_int_map.count(name) > 0 )
      return _long_int_map.find(name)->second;
    throw formic::Exception("no long int matching name \"%s\" in InputBase::get") % name;
  }

  ///  \brief  function to get double  options by name
  template <> const double  & InputBase::get<double >(const std::string & name) const {
    if ( _double_map.count(name) > 0 )
      return _double_map.find(name)->second;
    throw formic::Exception("no double matching name \"%s\" in InputBase::get") % name;
  }

  ///  \brief  function to get std::complex<double>  options by name
  template <> const std::complex<double>  & InputBase::get<std::complex<double> >(const std::string & name) const {
    if ( _complex_map.count(name) > 0 )
      return _complex_map.find(name)->second;
    throw formic::Exception("no std::complex<double> matching name \"%s\" in InputBase::get") % name;
  }

  ///  \brief  function to get std::string  options by name
  template <> const std::string  & InputBase::get<std::string >(const std::string & name) const {
    if ( _string_map.count(name) > 0 )
      return _string_map.find(name)->second;
    throw formic::Exception("no std::string matching name \"%s\" in InputBase::get") % name;
  }

  ///  \brief  function to get std::vector<int>  options by name
  template <> const std::vector<int>  & InputBase::get<std::vector<int> >(const std::string & name) const {
    if ( _vec_int_map.count(name) > 0 )
      return _vec_int_map.find(name)->second;
    throw formic::Exception("no std::vector<int> matching name \"%s\" in InputBase::get") % name;
  }

  ///  \brief  function to get std::vector<long int>  options by name
  template <> const std::vector<long int>  & InputBase::get<std::vector<long int> >(const std::string & name) const {
    if ( _vec_long_int_map.count(name) > 0 )
      return _vec_long_int_map.find(name)->second;
    throw formic::Exception("no std::vector<long int> matching name \"%s\" in InputBase::get") % name;
  }

  ///  \brief  function to get std::vector<double>  options by name
  template <> const std::vector<double>  & InputBase::get<std::vector<double> >(const std::string & name) const {
    if ( _vec_double_map.count(name) > 0 )
      return _vec_double_map.find(name)->second;
    throw formic::Exception("no std::vector<double> matching name \"%s\" in InputBase::get") % name;
  }

  ///  \brief  function to get std::vector<std::complex<double> >  options by name
  template <> const std::vector<std::complex<double> >  & InputBase::get<std::vector<std::complex<double> > >(const std::string & name) const {
    if ( _vec_complex_map.count(name) > 0 )
      return _vec_complex_map.find(name)->second;
    throw formic::Exception("no std::vector<std::complex<double> > matching name \"%s\" in InputBase::get") % name;
  }

  ///  \brief  function to get std::vector<std::string>  options by name
  template <> const std::vector<std::string>  & InputBase::get<std::vector<std::string> >(const std::string & name) const {
    if ( _vec_string_map.count(name) > 0 )
      return _vec_string_map.find(name)->second;
    throw formic::Exception("no std::vector<std::string> matching name \"%s\" in InputBase::get") % name;
  }

}

///  \brief  InputBase conversion to bool
formic::InputBase::operator bool() {

  bool retval;
  const std::string ret_type = "bool";

  if (_type_of_next == "bool")
    retval = _next_bool;
  else if (_type_of_next == "int")
    retval = bool(_next_int);
  else if (_type_of_next == "long int")
    retval = bool(_next_long_int);
  else
    throw formic::Exception("cannot convert type \"%s\" to type \"%s\" in InputBase conversion") % _type_of_next % ret_type;

  _type_of_next.clear();

  return retval;

}

///  \brief  InputBase conversion to int
formic::InputBase::operator int() {

  int retval;
  const std::string ret_type = "int";

  if (_type_of_next == "bool")
    retval = int(_next_bool);
  else if (_type_of_next == "int")
    retval = int(_next_int);
  else if (_type_of_next == "long int")
    retval = int(_next_long_int);
  else if (_type_of_next == "double")
    retval = int(_next_double);
  else
    throw formic::Exception("cannot convert type \"%s\" to type \"%s\" in InputBase conversion") % _type_of_next % ret_type;

  _type_of_next.clear();

  return retval;

}

///  \brief  InputBase conversion to long int
formic::InputBase::operator long int() {

  long int retval;
  const std::string ret_type = "long int";

  if (_type_of_next == "bool")
    retval = (long int)(_next_bool);
  else if (_type_of_next == "int")
    retval = (long int)(_next_int);
  else if (_type_of_next == "long int")
    retval = (long int)(_next_long_int);
  else if (_type_of_next == "double")
    retval = (long int)(_next_double);
  else
    throw formic::Exception("cannot convert type \"%s\" to type \"%s\" in InputBase conversion") % _type_of_next % ret_type;

  _type_of_next.clear();

  return retval;

}

///  \brief  InputBase conversion to unsigned int
formic::InputBase::operator unsigned int() {

  unsigned int retval;
  const std::string ret_type = "unsigned int";

  if (_type_of_next == "bool")
    retval = (unsigned int)(_next_bool);
  else if (_type_of_next == "int")
    retval = (unsigned int)(_next_int);
  else if (_type_of_next == "long int")
    retval = (unsigned int)(_next_long_int);
  else if (_type_of_next == "double")
    retval = (unsigned int)(_next_double);
  else
    throw formic::Exception("cannot convert type \"%s\" to type \"%s\" in InputBase conversion") % _type_of_next % ret_type;

  _type_of_next.clear();

  return retval;

}

///  \brief  InputBase conversion to double
formic::InputBase::operator double() {

  double retval;
  const std::string ret_type = "double";

  if (_type_of_next == "int")
    retval = (double)(_next_int);
  else if (_type_of_next == "long int")
    retval = (double)(_next_long_int);
  else if (_type_of_next == "double")
    retval = (double)(_next_double);
  else
    throw formic::Exception("cannot convert type \"%s\" to type \"%s\" in InputBase conversion") % _type_of_next % ret_type;

  _type_of_next.clear();

  return retval;

}

///  \brief  InputBase conversion to std::complex<double>
formic::InputBase::operator std::complex<double>() {

  std::complex<double> retval;
  const std::string ret_type = "std::complex<double>";

  if (_type_of_next == "int")
    retval = (std::complex<double>)(double(_next_int));
  else if (_type_of_next == "long int")
    retval = (std::complex<double>)(double(_next_long_int));
  else if (_type_of_next == "double")
    retval = (std::complex<double>)(_next_double);
  else if (_type_of_next == "std::complex<double>")
    retval = (std::complex<double>)(_next_complex);
  else
    throw formic::Exception("cannot convert type \"%s\" to type \"%s\" in InputBase conversion") % _type_of_next % ret_type;

  _type_of_next.clear();

  return retval;

}

///  \brief  InputBase conversion to std::string
formic::InputBase::operator std::string() {

  std::string retval;
  const std::string ret_type = "std::string";

  if (_type_of_next == "std::string")
    retval = _next_string;
  else
    throw formic::Exception("cannot convert type \"%s\" to type \"%s\" in InputBase conversion") % _type_of_next % ret_type;

  _type_of_next.clear();

  return retval;

}

///  \brief  InputBase conversion to std::vector<int>
formic::InputBase::operator std::vector<int>() {

  std::vector<int> retval;
  const std::string ret_type = "std::vector<int>";

  if (_type_of_next == "std::vector<int>")
    retval = _next_vec_int;
  else
    throw formic::Exception("cannot convert type \"%s\" to type \"%s\" in InputBase conversion") % _type_of_next % ret_type;

  _type_of_next.clear();

  return retval;

}

///  \brief  InputBase conversion to std::vector<long int>
formic::InputBase::operator std::vector<long int>() {

  std::vector<long int> retval;
  const std::string ret_type = "std::vector<long int>";

  if (_type_of_next == "std::vector<long int>")
    retval = _next_vec_long_int;
  else
    throw formic::Exception("cannot convert type \"%s\" to type \"%s\" in InputBase conversion") % _type_of_next % ret_type;

  _type_of_next.clear();

  return retval;

}

///  \brief  InputBase conversion to std::vector<double>
formic::InputBase::operator std::vector<double>() {

  std::vector<double> retval;
  const std::string ret_type = "std::vector<double>";

  if (_type_of_next == "std::vector<double>")
    retval = _next_vec_double;
  else
    throw formic::Exception("cannot convert type \"%s\" to type \"%s\" in InputBase conversion") % _type_of_next % ret_type;

  _type_of_next.clear();

  return retval;

}

///  \brief  InputBase conversion to std::vector<std::complex<double> >
formic::InputBase::operator std::vector<std::complex<double> >() {

  std::vector<std::complex<double> > retval;
  const std::string ret_type = "std::vector<std::complex<double> >";

  if (_type_of_next == "std::vector<std::complex<double> >")
    retval = _next_vec_complex;
  else
    throw formic::Exception("cannot convert type \"%s\" to type \"%s\" in InputBase conversion") % _type_of_next % ret_type;

  _type_of_next.clear();

  return retval;

}

///  \brief  InputBase conversion to std::vector<std::string>
formic::InputBase::operator std::vector<std::string>() {

  std::vector<std::string> retval;
  const std::string ret_type = "std::vector<std::string>";

  if (_type_of_next == "std::vector<std::string>")
    retval = _next_vec_string;
  else
    throw formic::Exception("cannot convert type \"%s\" to type \"%s\" in InputBase conversion") % _type_of_next % ret_type;

  _type_of_next.clear();

  return retval;

}
